/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.fix; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import org.eclipse.core.runtime.CoreException; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ConditionalExpression; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.InstanceofExpression; import org.eclipse.jdt.core.dom.ParenthesizedExpression; import org.eclipse.jdt.core.dom.InfixExpression.Operator; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.internal.corext.refactoring.code.OperatorPrecedence; import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; import org.eclipse.jdt.internal.corext.refactoring.util.NoCommentSourceRangeComputer; import org.eclipse.jdt.ui.cleanup.ICleanUpFix; public class ExpressionsFix extends CompilationUnitRewriteOperationsFix { private static final class MissingParenthesisVisitor extends ASTVisitor { private final ArrayList fNodes; private MissingParenthesisVisitor(ArrayList nodes) { fNodes= nodes; } public void postVisit(ASTNode node) { if (needsParentesis(node)) { fNodes.add(node); } } private boolean needsParentesis(ASTNode node) { // check that parent is && or || if (!(node.getParent() instanceof InfixExpression)) return false; // we want to add parenthesis around arithmetic operators and instanceof if (node instanceof InstanceofExpression) return true; if (node instanceof InfixExpression) { InfixExpression expression = (InfixExpression) node; InfixExpression.Operator operator = expression.getOperator(); InfixExpression parentExpression = (InfixExpression) node.getParent(); InfixExpression.Operator parentOperator = parentExpression.getOperator(); if (parentOperator == operator) return false; return (operator == InfixExpression.Operator.LESS) || (operator == InfixExpression.Operator.GREATER) || (operator == InfixExpression.Operator.LESS_EQUALS) || (operator == InfixExpression.Operator.GREATER_EQUALS) || (operator == InfixExpression.Operator.EQUALS) || (operator == InfixExpression.Operator.NOT_EQUALS) || (operator == InfixExpression.Operator.CONDITIONAL_AND) || (operator == InfixExpression.Operator.CONDITIONAL_OR); } return false; } } private static final class UnnecessaryParenthesisVisitor extends ASTVisitor { private final ArrayList fNodes; private UnnecessaryParenthesisVisitor(ArrayList nodes) { fNodes= nodes; } public boolean visit(ParenthesizedExpression node) { if (canRemoveParenthesis(node)) { fNodes.add(node); } return true; } /* * Can the parenthesis around node be removed? */ private boolean canRemoveParenthesis(ParenthesizedExpression node) { ASTNode parent= node.getParent(); if (!(parent instanceof Expression)) return true; Expression parentExpression= (Expression) parent; if (parentExpression instanceof ParenthesizedExpression) return true; Expression expression= getExpression(node); int expressionPrecedence= OperatorPrecedence.getExpressionPrecedence(expression); int parentPrecedence= OperatorPrecedence.getExpressionPrecedence(parentExpression); if (expressionPrecedence > parentPrecedence) //(opEx) opParent and opEx binds more -> can safely remove return true; if (expressionPrecedence < parentPrecedence) //(opEx) opParent and opEx binds less -> do not remove return false; //(opEx) opParent binds equal if (parentExpression instanceof InfixExpression) { InfixExpression parentInfix= (InfixExpression) parentExpression; if (parentInfix.getLeftOperand() == node) { //we have (expr op expr) op expr //infix expressions are evaluated from left to right -> can safely remove return true; } else if (isAssociative(parentInfix)) { //we have parent op (expr op expr) and op is associative //left op (right) == (right) op left == right op left if (expression instanceof InfixExpression) { InfixExpression infixExpression= (InfixExpression) expression; Operator operator= infixExpression.getOperator(); if (parentInfix.getOperator() != InfixExpression.Operator.TIMES) return true; if (operator == InfixExpression.Operator.TIMES) // x * (y * z) == x * y * z return true; if (operator == InfixExpression.Operator.REMAINDER) // x * (y % z) != x * y % z return false; //x * (y / z) == z * y / z iff no rounding ITypeBinding binding= infixExpression.resolveTypeBinding(); if (binding == null) return false; if (!binding.isPrimitive()) return false; String name= binding.getName(); if (isIntegerNumber(name)) //rounding involved return false; return true; } return true; } else { return false; } } else if (parentExpression instanceof ConditionalExpression) { ConditionalExpression conditionalExpression= (ConditionalExpression) parentExpression; if (conditionalExpression.getElseExpression() != node) return false; } return true; } private boolean isIntegerNumber(String name) { return "int".equals(name) || "long".equals(name) || "byte".equals(name) || "char".equals(name) || "short".equals(name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ } /* * Get the expression wrapped by the parentheses * i.e. ((((expression)))) -> expression */ private Expression getExpression(ParenthesizedExpression node) { Expression expression= node.getExpression(); while (expression instanceof ParenthesizedExpression) { expression= ((ParenthesizedExpression) expression).getExpression(); } return expression; } /** * Is the given expression associative? * <p> * This is true if and only if:<br> * <code>left operator (right) == (right) operator left == right operator left</code> * </p> * * @param expression the expression to inspect * @return true if expression is associative */ public static boolean isAssociative(InfixExpression expression) { Operator operator= expression.getOperator(); if (operator == InfixExpression.Operator.PLUS) { return isAllOperandsHaveSameType(expression); } if (operator == Operator.LESS || operator == Operator.LESS_EQUALS || operator == Operator.GREATER || operator == Operator.GREATER_EQUALS) { return isAllOperandsHaveSameType(expression); } if (operator == InfixExpression.Operator.CONDITIONAL_AND) return true; if (operator == InfixExpression.Operator.CONDITIONAL_OR) return true; if (operator == InfixExpression.Operator.AND) return true; if (operator == InfixExpression.Operator.OR) return true; if (operator == InfixExpression.Operator.XOR) return true; if (operator == InfixExpression.Operator.TIMES) return true; return false; } /* * Do all operands in expression have same type */ private static boolean isAllOperandsHaveSameType(InfixExpression expression) { ITypeBinding binding= expression.getLeftOperand().resolveTypeBinding(); if (binding == null) return false; ITypeBinding current= expression.getRightOperand().resolveTypeBinding(); if (binding != current) return false; for (Iterator iterator= expression.extendedOperands().iterator(); iterator.hasNext();) { Expression operand= (Expression) iterator.next(); current= operand.resolveTypeBinding(); if (binding != current) return false; } return true; } } private static class AddParenthesisOperation extends CompilationUnitRewriteOperation { private final Expression[] fExpressions; public AddParenthesisOperation(Expression[] expressions) { fExpressions= expressions; } /** * {@inheritDoc} */ public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException { TextEditGroup group= createTextEditGroup(FixMessages.ExpressionsFix_addParanoiacParenthesis_description, cuRewrite); ASTRewrite rewrite= cuRewrite.getASTRewrite(); AST ast= cuRewrite.getRoot().getAST(); for (int i= 0; i < fExpressions.length; i++) { // add parenthesis around expression Expression expression= fExpressions[i]; ParenthesizedExpression parenthesizedExpression= ast.newParenthesizedExpression(); parenthesizedExpression.setExpression((Expression) rewrite.createCopyTarget(expression)); rewrite.replace(expression, parenthesizedExpression, group); } } } private static class RemoveParenthesisOperation extends CompilationUnitRewriteOperation { private final HashSet/*<ParenthesizedExpression>*/ fExpressions; public RemoveParenthesisOperation(HashSet expressions) { fExpressions= expressions; } /** * {@inheritDoc} */ public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException { TextEditGroup group= createTextEditGroup(FixMessages.ExpressionsFix_removeUnnecessaryParenthesis_description, cuRewrite); ASTRewrite rewrite= cuRewrite.getASTRewrite(); rewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer()); while (fExpressions.size() > 0) { ParenthesizedExpression parenthesizedExpression= (ParenthesizedExpression)fExpressions.iterator().next(); fExpressions.remove(parenthesizedExpression); ParenthesizedExpression down= parenthesizedExpression; while (fExpressions.contains(down.getExpression())) { down= (ParenthesizedExpression)down.getExpression(); fExpressions.remove(down); } ASTNode move= rewrite.createMoveTarget(down.getExpression()); ParenthesizedExpression top= parenthesizedExpression; while (fExpressions.contains(top.getParent())) { top= (ParenthesizedExpression)top.getParent(); fExpressions.remove(top); } rewrite.replace(top, move, group); } } } public static ExpressionsFix createAddParanoidalParenthesisFix(CompilationUnit compilationUnit, ASTNode[] coveredNodes) { if (coveredNodes == null) return null; if (coveredNodes.length == 0) return null; // check sub-expressions in fully covered nodes final ArrayList changedNodes = new ArrayList(); for (int i= 0; i < coveredNodes.length; i++) { ASTNode covered = coveredNodes[i]; if (covered instanceof InfixExpression) covered.accept(new MissingParenthesisVisitor(changedNodes)); } if (changedNodes.isEmpty()) return null; CompilationUnitRewriteOperation op= new AddParenthesisOperation((Expression[])changedNodes.toArray(new Expression[changedNodes.size()])); return new ExpressionsFix(FixMessages.ExpressionsFix_addParanoiacParenthesis_description, compilationUnit, new CompilationUnitRewriteOperation[] {op}); } public static ExpressionsFix createRemoveUnnecessaryParenthesisFix(CompilationUnit compilationUnit, ASTNode[] nodes) { // check sub-expressions in fully covered nodes final ArrayList changedNodes= new ArrayList(); for (int i= 0; i < nodes.length; i++) { ASTNode covered= nodes[i]; if (covered instanceof ParenthesizedExpression || covered instanceof InfixExpression) covered.accept(new UnnecessaryParenthesisVisitor(changedNodes)); } if (changedNodes.isEmpty()) return null; HashSet expressions= new HashSet(changedNodes); RemoveParenthesisOperation op= new RemoveParenthesisOperation(expressions); return new ExpressionsFix(FixMessages.ExpressionsFix_removeUnnecessaryParenthesis_description, compilationUnit, new CompilationUnitRewriteOperation[] {op}); } public static ICleanUpFix createCleanUp(CompilationUnit compilationUnit, boolean addParanoicParentesis, boolean removeUnnecessaryParenthesis) { if (addParanoicParentesis) { final ArrayList changedNodes = new ArrayList(); compilationUnit.accept(new MissingParenthesisVisitor(changedNodes)); if (changedNodes.isEmpty()) return null; CompilationUnitRewriteOperation op= new AddParenthesisOperation((Expression[])changedNodes.toArray(new Expression[changedNodes.size()])); return new ExpressionsFix(FixMessages.ExpressionsFix_add_parenthesis_change_name, compilationUnit, new CompilationUnitRewriteOperation[] {op}); } else if (removeUnnecessaryParenthesis) { final ArrayList changedNodes = new ArrayList(); compilationUnit.accept(new UnnecessaryParenthesisVisitor(changedNodes)); if (changedNodes.isEmpty()) return null; HashSet expressions= new HashSet(changedNodes); CompilationUnitRewriteOperation op= new RemoveParenthesisOperation(expressions); return new ExpressionsFix(FixMessages.ExpressionsFix_remove_parenthesis_change_name, compilationUnit, new CompilationUnitRewriteOperation[] {op}); } return null; } protected ExpressionsFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] fixRewriteOperations) { super(name, compilationUnit, fixRewriteOperations); } }